Coverage Report

Created: 2021-08-28 18:14

D:\git\skunkworks\herald-for-cpp\herald-tests\ranges-tests.cpp
Line
Count
Source
1
//  Copyright 2021 Herald Project Contributors
2
//  SPDX-License-Identifier: Apache-2.0
3
//
4
5
#include "catch.hpp"
6
7
#include <iterator>
8
#include <iostream>
9
10
#include "herald/herald.h"
11
12
1
TEST_CASE("ranges-iterator-proxy", "[ranges][iterator][proxy]") {
13
1
  SECTION("ranges-iterator-proxy") {
14
1
    herald::analysis::views::in_range<int> workingAge(18,65);
15
1
16
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages;
17
1
    ages.push(10,12);
18
1
    ages.push(20,14);
19
1
    ages.push(30,19);
20
1
    ages.push(40,45);
21
1
    ages.push(50,66);
22
1
    herald::analysis::views::iterator_proxy proxy(ages);
23
1
24
1
    REQUIRE(!proxy.ended());
25
1
    REQUIRE(*proxy == 12);
26
1
    ++proxy;
27
1
    REQUIRE(*proxy == 14);
28
1
    ++proxy;
29
1
    REQUIRE(*proxy == 19);
30
1
    ++proxy;
31
1
    REQUIRE(*proxy == 45);
32
1
    ++proxy;
33
1
    REQUIRE(*proxy == 66);
34
1
    ++proxy;
35
1
    REQUIRE(proxy.ended());
36
1
  }
37
1
}
38
39
1
TEST_CASE("ranges-filter-singlematch", "[ranges][singlematch]") {
40
1
  SECTION("ranges-filter-singlematch") {
41
1
    herald::analysis::views::in_range<int> firstCentenary(0,100);
42
1
43
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages;
44
1
    ages.push(10,12);
45
1
46
1
    herald::analysis::views::filter<herald::analysis::views::in_range<int>> workingAgeFilter(firstCentenary);
47
1
48
1
    auto iter = workingAgeFilter(ages);
49
1
    REQUIRE(!iter.ended());
50
1
    REQUIRE(*iter == 12);
51
1
    ++iter;
52
1
    REQUIRE(iter.ended());
53
1
  }
54
1
}
55
56
1
TEST_CASE("ranges-filter-allmatch", "[ranges][allmatch]") {
57
1
  SECTION("ranges-filter-allmatch") {
58
1
    herald::analysis::views::in_range<int> firstCentenary(0,100);
59
1
60
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages;
61
1
    ages.push(10,12);
62
1
    ages.push(20,14);
63
1
    ages.push(30,19);
64
1
    ages.push(40,45);
65
1
    ages.push(50,66);
66
1
67
1
    herald::analysis::views::filter<herald::analysis::views::in_range<int>> workingAgeFilter(firstCentenary);
68
1
69
1
    auto iter = workingAgeFilter(ages);
70
1
    REQUIRE(!iter.ended());
71
1
    REQUIRE(*iter == 12);
72
1
    ++iter;
73
1
    REQUIRE(!iter.ended());
74
1
    REQUIRE(*iter == 14);
75
1
    ++iter;
76
1
    REQUIRE(!iter.ended());
77
1
    REQUIRE(*iter == 19);
78
1
    ++iter;
79
1
    REQUIRE(!iter.ended());
80
1
    REQUIRE(*iter == 45);
81
1
    ++iter;
82
1
    REQUIRE(!iter.ended());
83
1
    REQUIRE(*iter == 66);
84
1
    ++iter;
85
1
    REQUIRE(iter.ended());
86
1
  }
87
1
}
88
89
1
TEST_CASE("ranges-filter-typed", "[ranges][typed]") {
90
1
  SECTION("ranges-filter-typed") {
91
1
    herald::analysis::views::in_range<int> workingAge(18,65);
92
1
93
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages;
94
1
    ages.push(10,12);
95
1
    ages.push(20,14);
96
1
    ages.push(30,19);
97
1
    ages.push(40,45);
98
1
    ages.push(50,66);
99
1
100
1
    herald::analysis::views::filter<herald::analysis::views::in_range<int>> workingAgeFilter(workingAge);
101
1
102
1
    auto iter = workingAgeFilter(ages);
103
1
    REQUIRE(!iter.ended());
104
1
    REQUIRE(*iter == 19);
105
1
    ++iter;
106
1
    REQUIRE(!iter.ended());
107
1
    REQUIRE(*iter == 45);
108
1
    ++iter;
109
1
    REQUIRE(iter.ended());
110
1
  }
111
1
}
112
113
1
TEST_CASE("ranges-filter-generic", "[ranges][generic]") {
114
1
  SECTION("ranges-filter-generic") {
115
1
    herald::analysis::views::in_range workingAge(18,65);
116
1
    
117
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages;
118
1
    ages.push(10,12);
119
1
    ages.push(20,14);
120
1
    ages.push(30,19);
121
1
    ages.push(40,45);
122
1
    ages.push(50,66);
123
1
124
1
    auto workingAges = ages 
125
1
                     | herald::analysis::views::filter(workingAge) 
126
1
                     | herald::analysis::views::to_view();
127
1
128
1
    auto iter = workingAges.begin();
129
1
    REQUIRE(iter != workingAges.end());
130
1
    REQUIRE(*iter == 19);
131
1
    ++iter;
132
1
    REQUIRE(*iter == 45);
133
1
134
1
    REQUIRE(workingAges.size() == 2);
135
1
    REQUIRE(workingAges[0] == 19);
136
1
    REQUIRE(workingAges[1] == 45);
137
1
  }
138
1
}
139
140
1
TEST_CASE("ranges-filter-multi", "[ranges][filter][multi]") {
141
1
  SECTION("ranges-filter-multi") {
142
1
    herald::analysis::views::in_range workingAge(18,65);
143
1
    herald::analysis::views::greater_than over21(21);
144
1
    
145
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages;
146
1
    ages.push(10,12);
147
1
    ages.push(20,14);
148
1
    ages.push(30,19);
149
1
    ages.push(40,45);
150
1
    ages.push(50,66);
151
1
152
1
    auto workingAges = ages 
153
1
                     | herald::analysis::views::filter(workingAge) 
154
1
                     | herald::analysis::views::filter(over21)
155
1
                     | herald::analysis::views::to_view();
156
1
157
1
    auto iter = workingAges.begin();
158
1
    REQUIRE(iter != workingAges.end());
159
1
    REQUIRE(*iter == 45);
160
1
    ++iter;
161
1
    REQUIRE(iter == workingAges.end());
162
1
163
1
    REQUIRE(workingAges.size() == 1);
164
1
    REQUIRE(workingAges[0] == 45);
165
1
  }
166
1
}
167
168
1
TEST_CASE("ranges-iterator-rssisamples", "[ranges][iterator][rssisamples][rssi]") {
169
1
  SECTION("ranges-iterator-rssisamples") {
170
1
    herald::analysis::views::in_range valid(-99,-10);
171
1
    herald::analysis::views::less_than strong(-59);
172
1
    
173
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl;
174
1
    sl.push(1234,-9);
175
1
    sl.push(1244,-60);
176
1
    sl.push(1265,-58);
177
1
    sl.push(1282,-61);
178
1
    sl.push(1294,-100);
179
1
180
1
    herald::analysis::views::iterator_proxy<herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5>> proxy(sl);
181
1
182
1
    REQUIRE(!proxy.ended());
183
1
    REQUIRE((*proxy).value == -9);
184
1
    ++proxy;
185
1
    REQUIRE((*proxy).value == -60);
186
1
    ++proxy;
187
1
    REQUIRE((*proxy).value == -58);
188
1
    ++proxy;
189
1
    REQUIRE((*proxy).value == -61);
190
1
    ++proxy;
191
1
    REQUIRE((*proxy).value == -100);
192
1
    ++proxy;
193
1
    REQUIRE(proxy.ended());
194
1
  }
195
1
}
196
197
1
TEST_CASE("ranges-listtoview", "[ranges][listtoview][rssi]") {
198
1
  SECTION("ranges-listtoview") {
199
1
    herald::analysis::views::in_range valid(-111,-1);
200
1
201
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl;
202
1
    sl.push(1234,-9);
203
1
    sl.push(1244,-60);
204
1
    sl.push(1265,-58);
205
1
    sl.push(1282,-61);
206
1
    sl.push(1294,-100);
207
1
208
1
    auto values = sl 
209
1
                | herald::analysis::views::filter(valid) // Providing a filter as a list cannot convert directly to a view (what's the point? It's already a collection!)
210
1
                | herald::analysis::views::to_view();
211
1
212
1
    auto iter = values.begin();
213
1
    REQUIRE(iter != values.end());
214
1
    REQUIRE((*iter).value.intValue() == -9);
215
1
    ++iter;
216
1
    REQUIRE((*iter).value.intValue() == -60);
217
1
    ++iter;
218
1
    REQUIRE((*iter).value.intValue() == -58);
219
1
    ++iter;
220
1
    REQUIRE((*iter).value.intValue() == -61);
221
1
    ++iter;
222
1
    REQUIRE((*iter).value.intValue() == -100);
223
1
    ++iter;
224
1
    REQUIRE(iter == values.end());
225
1
226
1
    REQUIRE(values.size() == 5);
227
1
    auto val0 = values[0].value.intValue();
228
1
    auto val1 = values[1].value.intValue();
229
1
    auto val2 = values[2].value.intValue();
230
1
    auto val3 = values[3].value.intValue();
231
1
    auto val4 = values[4].value.intValue();
232
1
    REQUIRE(val0 == -9);
233
1
    REQUIRE(val1 == -60);
234
1
    REQUIRE(val2 == -58);
235
1
    REQUIRE(val3 == -61);
236
1
    REQUIRE(val4 == -100);
237
1
  }
238
1
}
239
240
1
TEST_CASE("ranges-filter-multi-rssisamples", "[ranges][filter][multi][rssisamples][rssi]") {
241
1
  SECTION("ranges-filter-multi-rssisamples") {
242
1
    herald::analysis::views::in_range valid(-99,-10);
243
1
    herald::analysis::views::less_than strong(-59);
244
1
    
245
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl;
246
1
    sl.push(1234,-9);
247
1
    sl.push(1244,-60);
248
1
    sl.push(1265,-58);
249
1
    sl.push(1282,-61);
250
1
    sl.push(1294,-100);
251
1
252
1
    auto values = sl 
253
1
                | herald::analysis::views::filter(valid) 
254
1
                | herald::analysis::views::filter(strong)
255
1
                | herald::analysis::views::to_view();
256
1
257
1
    auto iter = values.begin();
258
1
    REQUIRE(iter != values.end());
259
1
    REQUIRE((*iter).value == -60);
260
1
    ++iter;
261
1
    REQUIRE((*iter).value == -61);
262
1
    ++iter;
263
1
    REQUIRE(iter == values.end());
264
1
265
1
    REQUIRE(values.size() == 2);
266
1
    auto val0 = values[0].value.intValue();
267
1
    auto val1 = values[1].value.intValue();
268
1
    REQUIRE(val0 == -60);
269
1
    REQUIRE(val1 == -61);
270
1
  }
271
1
}
272
273
1
TEST_CASE("ranges-filter-multi-rssitwosamples-nothingfiltered", "[ranges][filter][multi][rssitwosamples-nothingfiltered][rssi]") {
274
1
  SECTION("ranges-filter-multi-rssitwosamples-nothingfiltered") {
275
1
    herald::analysis::views::in_range valid(-99,-10);
276
1
    herald::analysis::views::less_than strong(-59);
277
1
    
278
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl;
279
1
    sl.push(1244,-60);
280
1
    sl.push(1282,-61);
281
1
282
1
    auto values = sl 
283
1
                | herald::analysis::views::filter(valid) 
284
1
                | herald::analysis::views::filter(strong)
285
1
                | herald::analysis::views::to_view();
286
1
287
1
    auto iter = values.begin();
288
1
    REQUIRE(iter != values.end());
289
1
    REQUIRE((*iter).value.intValue() == -60);
290
1
    ++iter;
291
1
    REQUIRE((*iter).value.intValue() == -61);
292
1
    ++iter;
293
1
    REQUIRE(iter == values.end());
294
1
295
1
    REQUIRE(values.size() == 2);
296
1
    auto val0 = values[0].value.intValue();
297
1
    auto val1 = values[1].value.intValue();
298
1
    REQUIRE(val0 == -60);
299
1
    REQUIRE(val1 == -61);
300
1
  }
301
1
}
302
303
1
TEST_CASE("ranges-filter-multi-rssisinglesample-nothingfiltered", "[ranges][filter][multi][rssisinglesample-nothingfiltered][rssi]") {
304
1
  SECTION("ranges-filter-multi-rssisinglesample-nothingfiltered") {
305
1
    herald::analysis::views::in_range valid(-99,-10);
306
1
    herald::analysis::views::less_than strong(-59);
307
1
    
308
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl;
309
1
    sl.push(1244,-60);
310
1
311
1
    auto values = sl 
312
1
                | herald::analysis::views::filter(valid) 
313
1
                | herald::analysis::views::filter(strong)
314
1
                | herald::analysis::views::to_view();
315
1
316
1
    auto iter = values.begin();
317
1
    REQUIRE(iter != values.end());
318
1
    REQUIRE((*iter).value == -60);
319
1
    ++iter;
320
1
    REQUIRE(iter == values.end());
321
1
322
1
    REQUIRE(values.size() == 1);
323
1
    auto val0 = values[0].value.intValue();
324
1
    REQUIRE(val0 == -60);
325
1
  }
326
1
}
327
328
1
TEST_CASE("ranges-filter-multi-rssinosample", "[ranges][filter][multi][rssinosample][rssi]") {
329
1
  SECTION("ranges-filter-multi-rssinosample") {
330
1
    herald::analysis::views::in_range valid(-99,-10);
331
1
    herald::analysis::views::less_than strong(-59);
332
1
    
333
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl;
334
1
335
1
    auto values = sl 
336
1
                | herald::analysis::views::filter(valid) 
337
1
                | herald::analysis::views::filter(strong)
338
1
                | herald::analysis::views::to_view();
339
1
340
1
    auto iter = values.begin();
341
1
    REQUIRE(iter == values.end());
342
1
343
1
    REQUIRE(values.size() == 0);
344
1
  }
345
1
}
346
347
1
TEST_CASE("ranges-filter-multi-summarise", "[ranges][filter][multi][summarise][rssi]") {
348
1
  SECTION("ranges-filter-multi-summarise") {
349
1
    herald::analysis::views::in_range valid(-99,-10);
350
1
    herald::analysis::views::less_than strong(-59);
351
1
    
352
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,20> sl;
353
1
    sl.push(1234,-9);
354
1
    sl.push(1244,-60);
355
1
    sl.push(1265,-58);
356
1
    sl.push(1282,-62);
357
1
    sl.push(1282,-68);
358
1
    sl.push(1282,-68);
359
1
    sl.push(1294,-100);
360
1
361
1
    using namespace herald::analysis::aggregates;
362
1
    auto values = sl 
363
1
                | herald::analysis::views::filter(valid) 
364
1
                | herald::analysis::views::filter(strong)
365
1
                | herald::analysis::views::to_view();
366
1
367
1
    auto summary = values 
368
1
                 | summarise<Mean,Mode,Variance>();
369
1
370
1
    auto mean = summary.get<Mean>();
371
1
    auto mode = summary.get<Mode>();
372
1
    auto var = summary.get<Variance>();
373
1
374
1
    REQUIRE(mean == -64.5); // note conversion from RSSI -> int then aggregate -> float
375
1
    REQUIRE(mode == -68);
376
1
    REQUIRE(var == 17); // Happens to be exact, but you may need take in to account floating point inaccuracy in the tail 
377
1
  }
378
1
}
379
380
1
TEST_CASE("ranges-filter-multi-since-summarise", "[ranges][filter][multi][since][summarise][rssi]") {
381
1
  SECTION("ranges-filter-multi-since-summarise") {
382
1
    herald::analysis::views::in_range valid(-99,-10);
383
1
    herald::analysis::views::less_than strong(-59);
384
1
    herald::analysis::views::since afterPoint(herald::datatype::Date{1245});
385
1
    
386
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,20> sl;
387
1
    sl.push(1234,-9);
388
1
    sl.push(1244,-60);
389
1
    sl.push(1265,-58);
390
1
    sl.push(1282,-62);
391
1
    sl.push(1282,-68);
392
1
    sl.push(1282,-68);
393
1
    sl.push(1294,-100);
394
1
395
1
    using namespace herald::analysis::aggregates;
396
1
    auto values = sl 
397
1
                | herald::analysis::views::filter(afterPoint)
398
1
                | herald::analysis::views::filter(valid) 
399
1
                | herald::analysis::views::filter(strong)
400
1
                | herald::analysis::views::to_view();
401
1
402
1
    auto summary = values 
403
1
                 | summarise<Mean,Mode,Variance>();
404
1
405
1
    auto mean = summary.get<Mean>();
406
1
    auto mode = summary.get<Mode>();
407
1
    auto var = summary.get<Variance>();
408
1
409
1
    REQUIRE(mean == -66); // note conversion from RSSI -> int then aggregate -> float
410
1
    REQUIRE(mode == -68);
411
1
    REQUIRE(var == 12); // Happens to be exact, but you may need take in to account floating point inaccuracy in the tail 
412
1
  }
413
1
}
414
415
1
TEST_CASE("ranges-distance-aggregate", "[ranges][distance][filter][multi][since][summarise][rssi][aggregate]") {
416
1
  SECTION("ranges-distance-aggregate") {
417
1
    herald::analysis::views::in_range valid(-99,-10);
418
1
    herald::analysis::views::less_than strong(-59);
419
1
    herald::analysis::views::since afterPoint(herald::datatype::Date{1245});
420
1
    
421
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,20> sl;
422
1
    sl.push(1234,-9);
423
1
    sl.push(1244,-60);
424
1
    sl.push(1265,-58);
425
1
    sl.push(1282,-62);
426
1
    sl.push(1282,-68);
427
1
    sl.push(1282,-68);
428
1
    sl.push(1294,-100);
429
1
430
1
    using namespace herald::analysis::aggregates;
431
1
    auto values = sl 
432
1
                | herald::analysis::views::filter(afterPoint)
433
1
                | herald::analysis::views::filter(valid) 
434
1
                | herald::analysis::views::filter(strong)
435
1
                | herald::analysis::views::to_view();
436
1
437
1
    auto summary = values // is an r-value here
438
1
                 | summarise<Mean,Mode,Variance>();
439
1
440
1
    auto mean = summary.get<Mean>();
441
1
    auto mode = summary.get<Mode>();
442
1
    auto var = summary.get<Variance>();
443
1
    auto sd = std::sqrt(var);
444
1
445
1
    // See second diagram at https://heraldprox.io/bluetooth/distance
446
1
    // i.e. https://heraldprox.io/images/distance-rssi-regression.png
447
1
    herald::analysis::algorithms::distance::FowlerBasic to_distance(-50, -24);
448
1
449
1
    auto distance = sl 
450
1
                  | herald::analysis::views::filter(afterPoint)
451
1
                  | herald::analysis::views::filter(valid) 
452
1
                  | herald::analysis::views::filter(strong)
453
1
                  | herald::analysis::views::filter(
454
1
                      herald::analysis::views::in_range(
455
1
                        mode - 2*sd, // NOTE: WE USE THE MODE FOR FILTER, BUT SD FOR BOUNDS - See website for the reasoning
456
1
                        mode + 2*sd
457
1
                      )
458
1
                    )
459
1
                  // | herald::analysis::views::to_view() // returns an l-value -> Have to wrap in a view here as we need an end iterator to evaluate in aggregate
460
1
                  | aggregate(to_distance); // type actually <herald::analysis::algorithms::distance::FowlerBasic>
461
1
    
462
1
    auto agg = distance.get<herald::analysis::algorithms::distance::FowlerBasic>();
463
1
    auto d = agg.reduce();
464
1
    REQUIRE((d > 5.623 && d < 5.624)); // double rounding
465
1
466
1
    // Now do the same for an in-line temporary aggregate...
467
1
    
468
1
    auto distance2 = sl 
469
1
                   | herald::analysis::views::filter(afterPoint)
470
1
                   | herald::analysis::views::filter(valid) 
471
1
                   | herald::analysis::views::filter(strong)
472
1
                   | herald::analysis::views::filter(
473
1
                       herald::analysis::views::in_range(
474
1
                         mode - 2*sd, // NOTE: WE USE THE MODE FOR FILTER, BUT SD FOR BOUNDS - See website for the reasoning
475
1
                         mode + 2*sd
476
1
                       )
477
1
                     )
478
1
                  //  | herald::analysis::views::to_view() // returns an l-value -> Now have a helper in aggregate so we don't need to use to_view
479
1
                   | aggregate(herald::analysis::algorithms::distance::FowlerBasic(-50, -24)); // TRYING WITH A TEMPORARY - CHECKING IT DOES STD::MOVE CORRECTLY
480
1
    
481
1
    auto agg2 = distance2.get<herald::analysis::algorithms::distance::FowlerBasic>();
482
1
    auto d2 = agg2.reduce();
483
1
    REQUIRE((d2 > 5.623 && d2 < 5.624)); // double rounding
484
1
  }
485
1
}
486
487
// Risk aggregation example implementation
488
1
TEST_CASE("ranges-risk-aggregate", "[ranges][risk][aggregate][no-filter]") {
489
1
  SECTION("ranges-risk-aggregate") {
490
1
    // First we simulate a list of actual distance samples over time, using a vector of pairs
491
1
    std::vector<std::pair<herald::datatype::Date,double>> sourceDistances;
492
1
    sourceDistances.emplace_back(1235,5.5);
493
1
    sourceDistances.emplace_back(1240,4.7);
494
1
    sourceDistances.emplace_back(1245,3.9);
495
1
    sourceDistances.emplace_back(1250,3.2);
496
1
    sourceDistances.emplace_back(1255,2.2);
497
1
    sourceDistances.emplace_back(1260,1.9);
498
1
    sourceDistances.emplace_back(1265,1.0);
499
1
    sourceDistances.emplace_back(1270,1.3);
500
1
    sourceDistances.emplace_back(1275,2.0);
501
1
    sourceDistances.emplace_back(1280,2.2);
502
1
503
1
    // The below would be in your aggregate handling code...
504
1
    herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<double>, 2> distanceList;
505
1
506
1
    // For n distances we maintain n-1 distance-risks in a list, and continuously add to it
507
1
    // (i.e. we don't recalculate risk over all previous time - too much data)
508
1
    // Instead we keep a distance-time number for this known 'contact' which lasts up to 15 minutes.
509
1
    // (i.e. when the mac address changes in Bluetooth)
510
1
    // We would then store that single risk-time number against that single contact ID - much less data!
511
1
    double timeScale = 1.0; // default is 1 second
512
1
    double distanceScale = 1.0; // default is 1 metre, not scaled
513
1
    double minimumDistanceClamp = 1.0; // As per Oxford Risk Model, anything < 1m ...
514
1
    double minimumRiskScoreAtClamp = 1.0; // ...equals a risk of 1.0, ...
515
1
    // double logScale = 1.0; // ... and falls logarithmically after that
516
1
    // NOTE: The above values are pick for testing and may not be epidemiologically accurate!
517
1
    herald::analysis::algorithms::risk::RiskAggregationBasic riskScorer(timeScale,distanceScale,minimumDistanceClamp,minimumRiskScoreAtClamp);
518
1
519
1
    using namespace herald::analysis::aggregates;
520
1
    
521
1
    // this does nothing other than initialise our riskSlice reference
522
1
    auto riskSlice = distanceList
523
1
                    // no filters or any other iterator-proxy style class here...
524
1
                    //| herald::analysis::views::to_view() // Now we can pipe lists straight in to aggregates and summarise calls
525
1
                    | aggregate(riskScorer); // moves riskScorer in to aggregate instance
526
1
527
1
    // Now generate a sequence of Risk Scores over time
528
1
    double interScore = 0.0;
529
1
    double firstNonZeroInterScore = 0.0;
530
10
    for (auto&[when,distance] : sourceDistances) {
531
10
      // A new distance has been calculated!
532
10
      distanceList.push(when,distance);
533
10
      // Let's see if we have a new risk score!
534
10
      riskSlice = distanceList
535
10
                // no filters or any other iterator-proxy style class here...
536
10
                | riskSlice;
537
10
      // Add to our exposure risk for THIS contact
538
10
      // Note: We're NOT resetting over time, as the riskScorer will hold our total risk exposure from us.
539
10
      //       We could instead extract this slice, store it in a counter, and reset the risk Scorer if
540
10
      //       we needed to alter the value somehow or add the risk slices themselves to a new list.
541
10
      //       Instead, we only do this for each contact in total (i.e. up to 15 minutes per riskScorer).
542
10
      auto& agg = riskSlice.get<herald::analysis::algorithms::risk::RiskAggregationBasic>();
543
10
      interScore = agg.reduce();
544
10
      if (firstNonZeroInterScore == 0.0 && 
interScore > 06
) {
545
1
        firstNonZeroInterScore = interScore;
546
1
      }
547
10
      INFO("RiskAggregationBasic inter score: " << interScore << " address of agg: " << &agg);
548
10
    }
549
1
550
1
    // Now we have the total for our 'whole contact duration', not scaled for how far in the past it is
551
1
    auto& agg = riskSlice.get<herald::analysis::algorithms::risk::RiskAggregationBasic>();
552
1
    double riskScore = agg.reduce();
553
1
    INFO("RiskAggregationBasic final score: " << riskScore << " address of agg: " << &agg);
554
1
    REQUIRE(interScore > 0.0); // final inter score should be non zero
555
1
    REQUIRE(riskScore > 0.0); // final score should be non zero
556
1
    REQUIRE(riskScore > firstNonZeroInterScore); // should be additive over time too
557
1
  }
558
1
}
559
560
// TODO Given a list of risk-distance numbers, and the approximate final time of that contact, calculate
561
//      a risk score when the risk of infection drops off linearly over 14 days. (like COVID-19)
562
//      (Ideally we'd have a more robust epidemiological model, but this will suffice for example purposes)